diff options
| author | soryu <soryu@soryu.co> | 2026-01-18 17:44:50 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-18 17:44:50 +0000 |
| commit | 869f21ee2efaefed6a5aa4fbd417c25df8dec02a (patch) | |
| tree | 2a90820ac817173e5b7154e0ba5e4f5d095f9613 /apps/mobile/app/task/[id].tsx | |
| parent | 219bca168508e1ea5e91e8a9ce98338afeddfbd2 (diff) | |
| download | soryu-869f21ee2efaefed6a5aa4fbd417c25df8dec02a.tar.gz soryu-869f21ee2efaefed6a5aa4fbd417c25df8dec02a.zip | |
Add React Native mobile app for Makima (#3)
* [WIP] Heartbeat checkpoint - 2026-01-18 02:58:27 UTC
* feat(mobile): complete mobile app integration and verification
- Add ThemeColors type export to Colors.ts for type safety
- Export SUPABASE_URL from supabase.ts and use environment variables
- Update .env.example with correct default URLs
- Add comprehensive README.md with setup instructions
Verified:
- TypeScript compiles without errors
- App exports successfully for iOS and Android
- All screens accessible (login, dashboard, tasks, settings, task detail)
- Auth flow working with Zustand store and Supabase
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Task completion checkpoint
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'apps/mobile/app/task/[id].tsx')
| -rw-r--r-- | apps/mobile/app/task/[id].tsx | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/apps/mobile/app/task/[id].tsx b/apps/mobile/app/task/[id].tsx new file mode 100644 index 0000000..121063a --- /dev/null +++ b/apps/mobile/app/task/[id].tsx @@ -0,0 +1,259 @@ +import React from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + useColorScheme, + ActivityIndicator, +} from 'react-native'; +import { useLocalSearchParams, Stack } from 'expo-router'; +import { Colors } from '../../constants/Colors'; +import { useTask } from '../../hooks/useTasks'; +import { TaskStatusBadge } from '../../components/TaskStatusBadge'; +import { EmptyState } from '../../components/EmptyState'; + +export default function TaskDetailScreen() { + const colorScheme = useColorScheme() ?? 'light'; + const colors = Colors[colorScheme]; + const { id } = useLocalSearchParams<{ id: string }>(); + + const { data: task, isLoading, isError } = useTask(id); + + if (isLoading) { + return ( + <> + <Stack.Screen options={{ title: 'Loading...' }} /> + <View style={[styles.container, styles.centered, { backgroundColor: colors.background }]}> + <ActivityIndicator size="large" color={colors.tint} /> + </View> + </> + ); + } + + if (isError || !task) { + return ( + <> + <Stack.Screen options={{ title: 'Error' }} /> + <View style={[styles.container, { backgroundColor: colors.background }]}> + <EmptyState + icon="alert-circle-outline" + title="Failed to load task" + message="The task could not be found or an error occurred" + /> + </View> + </> + ); + } + + return ( + <> + <Stack.Screen options={{ title: task.name }} /> + <ScrollView + style={[styles.container, { backgroundColor: colors.background }]} + contentContainerStyle={styles.content} + > + {/* Header */} + <View style={[styles.header, { backgroundColor: colors.card }]}> + <View style={styles.headerTop}> + <TaskStatusBadge status={task.status} showLabel size="large" /> + </View> + <Text style={[styles.taskName, { color: colors.text }]}> + {task.name} + </Text> + {task.description && ( + <Text style={[styles.description, { color: colors.secondaryText }]}> + {task.description} + </Text> + )} + </View> + + {/* Progress Summary */} + {task.progressSummary && ( + <View style={[styles.section, { backgroundColor: colors.card }]}> + <Text style={[styles.sectionTitle, { color: colors.text }]}> + Progress + </Text> + <Text style={[styles.progressText, { color: colors.secondaryText }]}> + {task.progressSummary} + </Text> + </View> + )} + + {/* Task Info */} + <View style={[styles.section, { backgroundColor: colors.card }]}> + <Text style={[styles.sectionTitle, { color: colors.text }]}> + Details + </Text> + + <View style={styles.infoRow}> + <Text style={[styles.infoLabel, { color: colors.secondaryText }]}> + Created + </Text> + <Text style={[styles.infoValue, { color: colors.text }]}> + {new Date(task.createdAt).toLocaleString()} + </Text> + </View> + + {task.startedAt && ( + <View style={styles.infoRow}> + <Text style={[styles.infoLabel, { color: colors.secondaryText }]}> + Started + </Text> + <Text style={[styles.infoValue, { color: colors.text }]}> + {new Date(task.startedAt).toLocaleString()} + </Text> + </View> + )} + + {task.completedAt && ( + <View style={styles.infoRow}> + <Text style={[styles.infoLabel, { color: colors.secondaryText }]}> + Completed + </Text> + <Text style={[styles.infoValue, { color: colors.text }]}> + {new Date(task.completedAt).toLocaleString()} + </Text> + </View> + )} + + {task.repositoryUrl && ( + <View style={styles.infoRow}> + <Text style={[styles.infoLabel, { color: colors.secondaryText }]}> + Repository + </Text> + <Text + style={[styles.infoValue, { color: colors.tint }]} + numberOfLines={1} + > + {task.repositoryUrl} + </Text> + </View> + )} + </View> + + {/* Subtasks */} + {task.subtasks && task.subtasks.length > 0 && ( + <View style={[styles.section, { backgroundColor: colors.card }]}> + <Text style={[styles.sectionTitle, { color: colors.text }]}> + Subtasks ({task.subtasks.length}) + </Text> + {task.subtasks.map((subtask) => ( + <View key={subtask.id} style={styles.subtaskRow}> + <TaskStatusBadge status={subtask.status} size="small" /> + <Text + style={[styles.subtaskName, { color: colors.text }]} + numberOfLines={1} + > + {subtask.name} + </Text> + </View> + ))} + </View> + )} + + {/* Error message */} + {task.errorMessage && ( + <View style={[styles.section, styles.errorSection]}> + <Text style={[styles.sectionTitle, { color: '#991b1b' }]}> + Error + </Text> + <Text style={styles.errorText}>{task.errorMessage}</Text> + </View> + )} + + {/* Placeholder for future features */} + <View style={styles.placeholder}> + <Text style={[styles.placeholderText, { color: colors.secondaryText }]}> + Task output and controls will be added here + </Text> + </View> + </ScrollView> + </> + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + centered: { + justifyContent: 'center', + alignItems: 'center', + }, + content: { + padding: 16, + gap: 16, + }, + header: { + padding: 16, + borderRadius: 12, + gap: 8, + }, + headerTop: { + flexDirection: 'row', + justifyContent: 'flex-start', + }, + taskName: { + fontSize: 20, + fontWeight: '700', + }, + description: { + fontSize: 14, + lineHeight: 20, + }, + section: { + padding: 16, + borderRadius: 12, + gap: 12, + }, + sectionTitle: { + fontSize: 16, + fontWeight: '600', + }, + progressText: { + fontSize: 14, + lineHeight: 20, + }, + infoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + infoLabel: { + fontSize: 14, + }, + infoValue: { + fontSize: 14, + fontWeight: '500', + flex: 1, + textAlign: 'right', + marginLeft: 16, + }, + subtaskRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + paddingVertical: 4, + }, + subtaskName: { + fontSize: 14, + flex: 1, + }, + errorSection: { + backgroundColor: '#fee2e2', + }, + errorText: { + fontSize: 14, + color: '#991b1b', + lineHeight: 20, + }, + placeholder: { + padding: 32, + alignItems: 'center', + }, + placeholderText: { + fontSize: 14, + textAlign: 'center', + }, +}); |
